/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
/*
Authors: Ram Ovadia 201374022 and Lior Fass 300069705
Project: HW Assignment 3 
Description: This is the implementation of the sorter thread module used in the project.  
*/
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

#include "sorterThread.h"

/*
* compares equality of tSlot slot and the tSlot member of the tPlane struct plane.       
* used to check if a baggage destination and a plane destination match. 
*
* Input:
* -----
* slot - tSlot pointer      
* plane - tPlane struct pointer, whose tSlot member
*
* Output:
* -----
* Return Value:
* 0 - equal.
* !0 - unequal.
*/
int comp_plane_slots(void *plane, void *slot)
{
	return memcmp(((tPlane *)plane)->dest, slot, SLOT_LEN);
}

/*
* safe increment (waiting on plane mutex) luggage count of a plane.          
*
* Input:
* -----      
* plane - tPlane struct pointer, whose luggage count member is to be incremented. 
*
* Output:
* -----
* Return Value:
* 0 - successful.
* 1 - wait on plane luggage sempahore failed.
*/
int plane_luggage_inc(tPlane *plane)
{
	DWORD wait_result = WaitForSingleObject(plane->luggage_mutex, INFINITE);
	if ( (wait_result == WAIT_FAILED) || (wait_result == WAIT_ABANDONED) )
	{
		return 1;
	}
	plane->luggage_count += 1;
	ReleaseMutex(plane->luggage_mutex);
	return 0;
}

/*
* creates a new tPlane plane, whose destination is slot, and appends it to List planes.        
*
* Input:
* -----
* planes - pointer to List to which plane will be appended.       
* slot - tSlot to become the plane's dest member. 
*
* Output:
* -----
* Return Value:
* 0 - successful. planes will be appended new plane of destination slot. 
* !0 - memory allocation failure or mutex creation failure.
*/
int push_new_plane(List* planes, tSlot *slot)
{
	tPlane *plane = (tPlane *)calloc(1, sizeof(tPlane));
	if (plane == NULL)
	{
		printf("memory allocation error in push_new_plane\n");
		return 1;
	}
	plane->dest = (tSlot *)calloc(1, sizeof(tSlot)*SLOT_LEN);
	if (plane->dest == NULL)
	{
		printf("memory allocation error in push_new_plane\n");
		free(plane);
		return 1;
	}
	memcpy(plane->dest, slot, SLOT_LEN);
	plane->luggage_count = 1;
	plane->luggage_mutex = CreateMutex(NULL, FALSE, NULL);
	if (plane->luggage_mutex == NULL)
	{
		printf("memory allocation failure at plane->luggage_mutex\n");
		free(plane->dest);
		free(plane);
		return 1;
	}
	if (List_push(planes, plane) == 1)
	{
		CloseHandle(plane->luggage_mutex);
		free(plane->dest);
		free(plane);
		return 1;
	}
	return 0;
}

/*
* Destroys the list used by the thread to manage baggage collection for sorting.         
*
* Input:
* -----
* iter_collection - pointer to List to be destroyed.       
* err_msg - error to be printed to std output. 
*
* Output:
* -----
* Return Value:
* 1.
* prints err_msg to std output.
*/
int terminate_thread(List *iter_collection, char *err_msg)
{
	if (iter_collection!=NULL)
	{
		List_destroy(iter_collection, free);
	}
	printf("%s", err_msg);
	return 1;
}

/*
* updates planes List (appendes new planes, or increments luggage count) 
* according to destinations of luggage collected by the thread in iter_collection List.         
*
* Input:
* -----
* planes - pointer to List to be updated.       
* iter_collection - pointer to List of tSlots representing the destinations of 
*                   the luggage collected by the sorting thread in the current iteration. 
* planes_push_mutex - mutex to be obtained if planes has to be appended with a plane to new desination.  
*
* Output:
* -----
* Return Value:
* 0 - successful. planes will be updated according to luggage obtained in iter_collection. 
*		List iter_collection is returned empty (all nodes and values freed).
* !0 - memory allocation failure or mutex creation failure. 
*/
int update_plane_list(List *iter_collection, List *planes, HANDLE planes_push_mutex)
{
	tPlane *plane; 
	LIST_FOREACH(iter_collection, first, next, cur){
		plane = (tPlane *)List_find(planes, comp_plane_slots, cur->value);
		if (plane != NULL)
		{
			//update luggage
			if (plane_luggage_inc(plane) == 1)
			{
				return 1;
			}
		}
		else
		{
			//create a new plane (remember to search again after write protect mutex is acquired)
			WaitForSingleObject(planes_push_mutex, INFINITE);
			plane = (tPlane *)List_find(planes, comp_plane_slots, cur->value);
			if (plane==NULL)
			{
				if (push_new_plane(planes, (tSlot *)(cur->value)) == 1)
				{
					return 1;
				}
			}
			else
			{
				plane_luggage_inc(plane);
			}
			ReleaseMutex(planes_push_mutex);
		}	
	}
	List_clear(iter_collection, free);
	return 0;
}

/*
* used when an unattended non-empty slot in the check-in array is encoutnered by the sorter thread.        
* unloads the slot from the array, appends it to iter_collection list, and replaces it with an EMPTY_SLOT char. 
*
* Input:
* -----
* slots - pointer to array of check-in slots.        
* iter_collection - pointer to List of tSlots representing the destinations of 
*                   the luggage collected by the sorting thread in the current iteration. 
* index - the index of the unattended non-empty check in slot in the array to be unloaded.   
*
* Output:
* -----
* Return Value:
* 0 - successful. iter_collection will be appended with the destination of the luggage 
*     unloaded from the tSlot in index. The check-in slot will be replaced with an EMPTY SLOT character.   
* !0 - memory allocation failure. 
*/
int unload_luggage(List* iter_collection, tSlot *slots, int index)
{
	tSlot *cur_element = (tSlot *)calloc(SLOT_LEN, sizeof(tSlot));
	if (cur_element == NULL)
	{
		printf("memory allocation error at unload_luggage\n");
		return 1;
	}
	memcpy(cur_element, slots+index, SLOT_LEN);
	if (List_push(iter_collection, cur_element) == 1)
	{
		free(cur_element);
		return 1;
	}
	memcpy(slots+index, EMPTY_SLOT, sizeof(EMPTY_SLOT));
	return 0;
}


int sorter_thread(void *args)
{
	int i = 0;
	DWORD wait_result;
	List *iter_collection = NULL;
	tArgs *common = (tArgs *)args;
	BOOL loader_complete = FALSE;
	DWORD loader_exit_code;

	// waiting for synchronization signal. 
	wait_result = WaitForSingleObject( common->sync_sempahore , INFINITE);
	if ( (wait_result == WAIT_ABANDONED) || (wait_result == WAIT_FAILED) )
	{
		return terminate_thread(iter_collection, "waiting on sync sempahore failed in sorter thread\r\n");
	}

	while (TRUE)
	{
		if ( GetExitCodeThread(common->loader_thread_handle,  &loader_exit_code) != 0)
		{
			if (loader_exit_code != STILL_ACTIVE) 
			{
				loader_complete = TRUE;
			}
		}
		iter_collection = List_create();
		if (iter_collection == NULL)
		{
			return terminate_thread(iter_collection,"list creation failed in sorter thread\r\n");
		}
		for (i=0; i<common->num_of_slots; i++)
		{
			wait_result = WaitForSingleObject(common->slots_mutexes[i],0);
			if (wait_result == WAIT_TIMEOUT)
			{
				continue;
			}
			else if (wait_result == WAIT_OBJECT_0)
			{
				if (strcmp(common->slots+i*SLOT_LEN, EMPTY_SLOT) != 0)
				{	
					// reached a non-empty slot, clearing slot. 
					if (unload_luggage(iter_collection, common->slots, i*SLOT_LEN) == 1)
					{
						return terminate_thread(iter_collection, "memory failure while load from slot\r\n");
					}
				}
				ReleaseMutex(common->slots_mutexes[i]);
			}
			else if ( (wait_result == WAIT_FAILED) || (wait_result == WAIT_ABANDONED) )
			{
				return terminate_thread(iter_collection, "waiting on slot sempahore failed in sorter thread\r\n");
			}
		}

		if (List_count(iter_collection) == 0)
		{
			// all slots were empty. 
			Sleep(10);
		} 
		else
		{
			update_plane_list(iter_collection, common->planes, common->planes_push_mutex);
		}

		// test for thread completion conditions. 
		if ((loader_complete == TRUE) && (List_count(iter_collection) == 0))
		{
			break;
		} 
	}
	terminate_thread(iter_collection, "");

	return 0;
}

